using System;
using System.IO;

namespace LumiSoft.Data.lsDB
{
	/// <summary>
	/// Data page.
	/// </summary>
	internal class DataPage
	{
		private DbFile     m_pOwnerDB             = null;
		private long       m_StartPointer         = 0;
		private bool       m_Used                 = false;
		private long       m_OwnerID              = 0;
		private long       m_OwnerDataPagePointer = 0;
		private long       m_NextDataPagePointer  = 0;
		private int        m_DataAreaSize         = 1000;
		private int        m_StoredDataLength     = 0;

		private byte[]     m_Data                 = null;

		/// <summary>
		/// Default constructor.
		/// </summary>
		/// <param name="dataPageDataAreaSize">Specifies how much data data page can store.</param>
		/// <param name="ownerDB">Owner DB file..</param>
		/// <param name="startOffset">Data page start offset pointer.</param>
		public DataPage(int dataPageDataAreaSize,DbFile ownerDB,long startOffset)
		{
			/* DataPage structure
			   2 bytes                 - CRLF
			   1 byte                  - used (f - unused,u - used)
			   8 byte	               - owner object id
			   8 bytes                 - owner data page pointer
			   8 bytes                 - continuing data page pointer
			   4 bytes                 - stored data length in data area
			   2 bytes                 - CRLF
			   1000 bytes              - data area
			*/
			
			m_DataAreaSize = dataPageDataAreaSize;
			m_pOwnerDB = ownerDB;
			m_StartPointer = startOffset;
		
			byte[] dataPageInfo = new byte[33];
			ownerDB.SetFilePosition(startOffset);
			ownerDB.ReadFromFile(dataPageInfo,0,dataPageInfo.Length);
			m_Data = new byte[dataPageDataAreaSize];
			ownerDB.ReadFromFile(m_Data,0,dataPageDataAreaSize);

			// CRLF
			if(dataPageInfo[0] != (byte)'\r'){
				throw new Exception("Not right data page startOffset, or invalid data page <CR> is expected but is '" + (int)dataPageInfo[0] + "' !");
			}
			if(dataPageInfo[1] != (byte)'\n'){
				throw new Exception("Not right data page startOffset, or invalid data page <LF> is expected but is '" + (int)dataPageInfo[1] + "' !");
			}

			// used
			if(dataPageInfo[2] == (byte)'u'){
				m_Used = true;
			}
			else{
				m_Used = false;
			}

			// owner object id
			m_OwnerID = ldb_Utils.ByteToLong(dataPageInfo,3);

			// owner data page pointer
			m_OwnerDataPagePointer = ldb_Utils.ByteToLong(dataPageInfo,11);

			// continuing data page pointer
			m_NextDataPagePointer = ldb_Utils.ByteToLong(dataPageInfo,19);

			// stored data length in data area
			m_StoredDataLength = ldb_Utils.ByteToInt(dataPageInfo,27);

			// CRLF
			if(dataPageInfo[31] != (byte)'\r'){
				throw new Exception("Not right data page startOffset, or invalid data page <CR> is expected but is '" + (int)dataPageInfo[31] + "' !");
			}
			if(dataPageInfo[32] != (byte)'\n'){
				throw new Exception("Not right data page startOffset, or invalid data page <LF> is expected but is '" + (int)dataPageInfo[32] + "' !");
			}
		}

		

		#region static method CreateDataPage

		/// <summary>
		/// Creates new data page structure.
		/// </summary>
		/// <param name="dataPageDataAreaSize">Specifies how much data can data page store.</param>
		/// <param name="used">Specifies if data page is used or free space. If this value is false, all toher parameters aren't stored.</param>
		/// <param name="ownerID">Owner data object ID.</param>
		/// <param name="ownerDataPagePointer">This data page owner data page pointer. This value can be 0, if no owner.</param>
		/// <param name="nextDataPagePointer">Data page pointer, what continues this data page. This value can be 0 if, data page won't spread to multiple data pages.</param>
		/// <param name="data">Data what data page stores. Maximum length is dataPageDataAreaSize.</param>
		/// <returns></returns>
		public static byte[] CreateDataPage(int dataPageDataAreaSize,bool used,long ownerID,long ownerDataPagePointer,long nextDataPagePointer,byte[] data)
		{
			/* DataPage structure
			   2 bytes                    - CRLF
			   1 byte                     - used (f - unused,u - used)
			   8 byte	                  - owner object id
			   8 bytes                    - owner data page pointer
			   8 bytes                    - continuing data page pointer
			   4 bytes                    - stored data length in data area
			   2 bytes                    - CRLF
			   dataPageDataAreaSize bytes - data area
			*/

			if(data.Length > dataPageDataAreaSize){
				throw new Exception("Data page can store only " + dataPageDataAreaSize + " bytes, data conatins '" + data.Length + "' bytes !");
			}

			byte[] dataPage = new byte[dataPageDataAreaSize + 33];
			// CRLF
			dataPage[0] = (byte)'\r';
			dataPage[1] = (byte)'\n';
			if(used){
				// used
				dataPage[2] = (byte)'u';
				// owner object id
				Array.Copy(ldb_Utils.LongToByte(ownerID),0,dataPage,3,8);
				// owner data page pointer
				Array.Copy(ldb_Utils.LongToByte(ownerDataPagePointer),0,dataPage,11,8);
				// continuing data page pointer
				Array.Copy(ldb_Utils.LongToByte(nextDataPagePointer),0,dataPage,19,8);
				// stored data length in data area
				Array.Copy(ldb_Utils.IntToByte(data.Length),0,dataPage,27,4);
				// CRLF
				dataPage[31] = (byte)'\r';
				dataPage[32] = (byte)'\n';
				// data area
				Array.Copy(data,0,dataPage,33,data.Length);
			}
			else{
				// used
				dataPage[2] = (byte)'f';
				// CRLF
				dataPage[31] = (byte)'\r';
				dataPage[32] = (byte)'\n';
			}

			return dataPage;
		}

		#endregion


		#region method ReadData

		/// <summary>
		/// Reads specified amount data to buffer.
		/// </summary>
		/// <param name="buffer">Buffer where to store data.</param>
		/// <param name="startIndexInBuffer">Start index in buffer where data storing begins. Start index is included.</param>
		/// <param name="length">Number of bytes to read.</param>
		/// <param name="startOffset">Zero based offset of data area.</param>
		/// <returns></returns>
		public void ReadData(byte[] buffer,int startIndexInBuffer,int length,int startOffset)
		{
			if(startOffset < 0){
				throw new Exception("startOffset can't negative value !");
			}
			if((length + startOffset) > this.DataAreaSize){
				throw new Exception("startOffset and length are out of range data page data area !");
			}
			if((length + startOffset) > m_StoredDataLength){
				throw new Exception("There isn't so much data stored in data page as requested ! Stored data length = " + m_StoredDataLength + "; start offset = " + startOffset + "; length wanted = " + length);
			}

			Array.Copy(m_Data,startOffset,buffer,startIndexInBuffer,length);
		}

		/// <summary>
		/// Reads data page data. Offset byte is included.
		/// </summary>
		/// <param name="startOffset">Zero based offset of data area.</param>
		/// <param name="length">Specifies how much data to read.</param>
		/// <returns></returns>
		public byte[] ReadData(int startOffset,int length)
		{
			if(startOffset < 0){
				throw new Exception("startOffset can't negative value !");
			}
			if((length + startOffset) > this.DataAreaSize){
				throw new Exception("startOffset and length are out of range data page data area !");
			}
			if((length + startOffset) > m_StoredDataLength){
				throw new Exception("There isn't so much data stored in data page as requested ! Stored data length = " + m_StoredDataLength + "; start offset = " + startOffset + "; length wanted = " + length);
			}

			byte[] data = new byte[length];
			Array.Copy(m_Data,startOffset,data,0,length);

			return data; 
		}

		#endregion

		#region method WriteData

		/// <summary>
		/// Writed data to data page.
		/// </summary>
		/// <param name="data">Data to write.</param>
		public void WriteData(byte[] data)
		{
			if(data.Length > this.DataAreaSize){
				throw new Exception("Data page can't store more than " + this.DataAreaSize + " bytes, use mutliple data pages !");
			}
		
			// Set stored data length
			m_pOwnerDB.SetFilePosition(m_StartPointer + 27);
			m_pOwnerDB.WriteToFile(ldb_Utils.IntToByte(data.Length),0,4);
				
            // Store data
			m_pOwnerDB.SetFilePosition(m_StartPointer + 33);
			m_pOwnerDB.WriteToFile(data,0,data.Length);

			m_StoredDataLength = data.Length;
		}

		#endregion


		#region Properties Implementation

		/// <summary>
		/// Gets data page size on disk in bytes.
		/// </summary>
		public int DataPageSize
		{
			get{ return 33 + this.DataAreaSize; }
		}

		/// <summary>
		/// Gets this data page address (offset in database file).
		/// </summary>
		public long Pointer
		{
			get{ return m_StartPointer; }
		}

		/// <summary>
		/// Gets or sets if data page used or free space.
		/// </summary>
		public bool Used
		{
			get{ return m_Used; }

			set{
				m_pOwnerDB.SetFilePosition(m_StartPointer + 2);
				m_pOwnerDB.WriteToFile(new byte[]{Convert.ToByte(value)},0,1);
			}
		}

		/// <summary>
		/// Gets owner object id what owns this data page.
		/// </summary>
		public long OwnerID
		{
			get{ return m_OwnerID; }
		}

		/// <summary>
		/// Gets or sets owner data page pointer.
		/// Returns 0 if this is first data page of multiple data pages or only data page.
		/// </summary>
		public long OwnerDataPagePointer
		{
			get{ return m_OwnerDataPagePointer; }

			set{
				// owner data page pointer
				m_pOwnerDB.SetFilePosition(m_StartPointer + 11);
				m_pOwnerDB.WriteToFile(ldb_Utils.LongToByte(value),0,8);

				m_OwnerDataPagePointer = value;
			}
		}

		/// <summary>
		/// Gets or sets pointer to data page what continues this data page.
		/// Returns 0 if data page has enough room for data and there isn't continuing data page.
		/// </summary>
		public long NextDataPagePointer
		{
			get{ return m_NextDataPagePointer; }

			set{
				// continuing data page pointer
				m_pOwnerDB.SetFilePosition(m_StartPointer + 19);
				m_pOwnerDB.WriteToFile(ldb_Utils.LongToByte(value),0,8);

				m_NextDataPagePointer = value;
			}
		}
/*
		/// <summary>
		/// Gets or sets data that data page holds. Maximum size is this.DataAreaSize. Returns null if no data stored.
		/// </summary>
		public byte[] Data
		{
			get{ 
				byte[] data = new byte[m_StoredDataLength];
				m_pDbFileStream.Position = m_StartPointer + 33;
				m_pDbFileStream.Read(data,0,data.Length);

				return data; 
			}

			set{
				if(value.Length > this.DataAreaSize){
					throw new Exception("Data page can't store more than " + this.DataAreaSize + " bytes, use mutliple data pages !");
				}
		
				// Set stored data length
				m_pDbFileStream.Position = m_StartPointer + 27;
				byte[] dataLength = ldb_Utils.IntToByte(value.Length);
				m_pDbFileStream.Write(dataLength,0,dataLength.Length);
				
                // Store data
				m_pDbFileStream.Position = m_StartPointer + 33;
				m_pDbFileStream.Write(value,0,value.Length);

				m_StoredDataLength = value.Length;
			}
		}
*/
		/// <summary>
		/// Gets how many data data page can store.
		/// </summary>
		public int DataAreaSize
		{
			get{ return m_DataAreaSize; }
		}

		/// <summary>
		/// Gets stored data length.
		/// </summary>
		public int StoredDataLength
		{
			get{ return m_StoredDataLength; }
		}

		/// <summary>
		/// Gets how much free data space is availabe in data page.
		/// </summary>
		public long SpaceAvailable
		{
			get{ return this.DataAreaSize - m_StoredDataLength; }
		}

		#endregion

	}
}
